<?php

class App_Source_Structure_Mapper extends Mozg_Mapper_Data
{
	private $_nodes = array();
	private $_nodesRel = array();
	private $_nodesByUrl = array();
	private $_nodesByType = array();

	private $_isDataLoaded = false;

	private $_source;

	protected $_entityPath = 'entity/structure';

	public function __construct()
	{
		$this->_source = Mozg_Source::get('default');
	}

	private function CleanCache()
	{
		$this->_nodes = array();
		$this->_nodesByUrl = array();
		$this->_nodesRel = array();
		$this->_nodesByType = array();

		$this->_isDataLoaded = false;
	}

    private function GetAllData()
    {
    	if ($this->_isDataLoaded) return;
    	$this->CleanCache();

    	$stmt = $this->_source->PrepareStmt("SELECT
											" .$this->_source->Table('structure')->GetSqlMap(array('content_body' => 0, 'page_params' => 0, 'module_params' => 0)). "
										FROM
											%%categories%%
										ORDER BY parentId, cOrder");

		$entityConfig = Mozg_Property_Config::Get($this->_entityPath);

	    foreach ($stmt as $row)
        {
        	$obj = Mozg_Entity::Get($entityConfig, $row->ExportData());
			$id = $obj->GetId();

            $this->_nodes[$id] = $obj;
            $this->_nodesByUrl[$obj->GetFullUrl()] = $id;
            $this->_nodesRel[$obj->GetParentId()][] = $id;
            $this->_nodesByType[$obj->GetPageType()][] = $id;

            $obj->DataCallback(array($this, 'SetPageData'));
        }

        $this->_isDataLoaded = true;
    }

    public function CreateFullUrl($url, $parentUrl)
    {
		return (strlen($parentUrl) ? $parentUrl : '').urlencode($url). (strlen($url) ? '/' : '');
    }

    public function GetRoot()
    {
    	return $this->GetOne(array('url' => ''));
    }

    protected function FilterNodes(array $nodes, $ids)
    {
    	if (!is_array($ids)) $ids = array($ids);
    	return array_intersect_key($nodes, array_flip($ids));
    }

	public function GetList($filter = null, Mozg_Paginator $paginator = null)
	{
		return $this->Find($filter, $paginator);
	}

	public function GetOne($filter = null)
	{
		return current($this->Find($filter, new Mozg_Paginator(1,1)));
	}

	protected function Find($filter = null, Mozg_Paginator $paginator = null, $fields = array())
	{
		$this->GetAllData();

		if (!($filter instanceof Mozg_Property_Params)) $filter = $this->GetFilter($filter);

		$nodes = $this->_nodes;

		if ($filter->HasSet('url'))
		{
			$id = 0;
			if (isset($this->_nodesByUrl[$filter->GetUrl()]))
			{
				$id = $this->_nodesByUrl[$filter->GetUrl()];
			}

			$nodes = $this->FilterNodes($nodes, $id);
		}

		if ($filter->HasSet('id'))
		{
			$nodes = $this->FilterNodes($nodes, $filter->GetId());
		}

		if ($filter->HasSet('pageType'))
		{
			$ids = array();
			if (isset($this->_nodesByType[$filter->GetPageType()]))
			{
				$ids = $this->_nodesByType[$filter->GetPageType()];
			}

			$nodes = $this->FilterNodes($nodes, $ids);
		}

		if ($filter->HasSet('parentId'))
		{
			$ids = $this->GetSubElementsIds($filter->GetParentId(), $filter->GetLevels());
			$nodes = $this->FilterNodes($nodes, $ids);
		}

		if ($filter->HasSet('pathId'))
		{
			$ids = $this->GetParentElementsIds($filter->GetPathId());
			$nodes = $this->FilterNodes($nodes, $ids);
		}

		$fNodes = array();
		foreach ($nodes as $node)
		{
			if ((!$filter->HasSet('active')
				|| ($filter->GetActive() && $node->GetActive())
				|| (!$filter->GetActive() && !$node->GetActive()))
				&&
				(!$filter->HasSet('blocked')
				|| ($filter->GetBlocked() && $node->GetBlocked())
				|| (!$filter->GetBlocked() && !$node->GetBlocked())))
			{
				$fNodes[$node->GetId()] = clone $node;
			}
		}

        return $fNodes;
	}

    protected function GetParentElementsIds($id)
    {
    	$this->GetAllData();

    	$rootId = $this->_nodesByUrl[''];
    	if (!$rootId) return array();

    	$parentIds = array();
    	while (isset($this->_nodes[$id])
    			&&($element = $this->_nodes[$id])
    			&& $element->GetParentId()
    			&& ($element->GetParentId() != $rootId))
    	{
    		$parentIds[] = 	$element->GetParentId();
    		$id = $element->GetParentId();
    	}

    	return array_reverse($parentIds);
    }

    protected function GetSubElementsIds($parentId = 0, $levels = 0)
    {
    	$this->GetAllData();

        if (isset($this->_nodesRel[$parentId]))
        {
        	$sub = $this->_nodesRel[$parentId];
        }
        else
        {
        	$sub = array();
        }

        $levels = (int)$levels;
        if ($levels <= 0) return $sub;
        $levels--;

        $ssub = $sub;
        while (list (, $nCid) = each($sub))
        {
			$ssub = array_merge($ssub, $this->GetSubElementsIds($nCid, $levels));
        }

        return $ssub;
    }

    public function SetPageData($obj)
    {
    	if (!$obj->GetId()) return false;

    	$fields = array('content_body' => 1, 'page_params' => 1, 'module_params' => 1);

        $stmt = $this->_source->PrepareStmt("SELECT
        									" .$this->_source->Table('structure')->GetSqlMap($fields). "
										FROM
											%%categories%%
										WHERE cid = " .$obj->getId());

		return $stmt->fetch()->ExportData();
    }

	public function Save(Mozg_Property_Params $obj)
	{
		$this->_source->LockTable(array('%%categories%%'));
		$this->CleanCache();

		if ($obj->Validator()->Validate())
		{
			$this->Prepare($obj);

			if ($obj->GetId())
			{
				$filter = $this->GetFilter();
				$filter->SetId($obj->GetId());
				$status = $this->Update($obj, $filter);
			}
			else
			{
				$status = $this->Insert($obj);
			}
		}
		else
		{
			$status = false;
		}

		$this->_source->UnLockTable();

		return $status;
	}

	protected function Insert(Mozg_Property_Params $obj)
	{
		$fields = $this->_source->Table('structure')->GetSqlInsert($obj);
		//print_r($fields);exit;
		$num = $this->_source->exec("INSERT INTO %%categories%%(" .$fields['fields']. ") VALUES (" .$fields['values']. ")");

		if ($num)
		{
			$obj->SetId($this->_source->lastInsertID());
    		$this->CleanCache();

    		return true;
		}

		return false;
	}

    protected function Update(Mozg_Property_Params $obj, $filter = null)
    {
		$objects = $this->GetList($filter);
		if (sizeof($object) > 1) throw new Mozg_Exception_System('Categories mass update currently unsupported');

		$updRows = 0;
		foreach ($objects as $object)
		{
			$updateStr = $this->_source->Table('structure')->GetSqlUpdate($obj, '', null, array('update_time' => "now"));
			//echo $updateStr; exit;
			if (strlen($updateStr))
			{
		    	$numRows = $this->_source->exec("update %%categories%%
											              set
															  " .$updateStr. "
											             WHERE
											             	  cid = " .(int)$object->GetId(). "
											            ");

		    	if ($numRows)
		    	{
		    		$this->UpdateSubElementsFullUrls($object->GetFullUrl(), $obj->GetFullUrl());
		    		$updRows++;
		    	}
			}
			else
			{
				$updRows++;
			}
		}

		if ($updRows)
		{
			$this->CleanCache();
		}

        return $updRows;
    }

    private function UpdateSubElementsFullUrls($oldFullUrl, $newFullUrl)
    {
        if (!strlen($oldFullUrl) || ! strlen($newFullUrl) || ($oldFullUrl == $newFullUrl)) return;
        $this->_source->exec("UPDATE %%categories%%
        								set
                                            fullurl=INSERT(fullurl,1," . strlen($oldFullUrl) . "," . $this->_source->quote($newFullUrl) . ")
        								where
        									fullurl LIKE " . $this->_source->quote($oldFullUrl. "%"));
	}

    protected function Prepare(Mozg_Property_Params $obj)
    {
		$rootElem = $this->GetRoot();
		if ($rootElem && ($obj->GetId() != $rootElem->GetId()))
		{
			$parentItem = $this->GetOne(array('id' => $obj->GetParentId()));
			$parentUrl = $parentItem->GetFullUrl();
			$obj->SetFullUrl($this->CreateFullUrl($obj->GetUrl(), $parentUrl));

	    	$realItem = $this->GetOne(array('id' => $obj->GetId()));
			if (!$realItem || ($realItem->GetParentId() != $parentItem->GetId()))
			{
				$list = $this->GetList(array('parentId' => $obj->GetParentId()));
				$lastNode = array_pop($list);

				if ($lastNode)
				{
					$cOrder = $lastNode->GetCOrder() + 1;
				}
				else
				{
					$cOrder = 0;
				}

				$obj->SetCOrder($cOrder);
			}
		}
    }

    public function Sort(array $idsToSort)
    {
    	if (!sizeof($idsToSort)) return false;

    	$item = $this->GetOne(array('id' => $idsToSort[0]));
    	if (!$item) return false;

        $allItemsIds = $this->GetSubElementsIds($item->GetParentId());
        $idsToSort = array_values(array_intersect($idsToSort, $allItemsIds));
        if (sizeof($idsToSort) < 2) return false;

		$this->_source->exec("SET @pos = 0");
		$this->_source->exec("update %%categories%%
										      set
									              corder = (SELECT @pos := @pos + 1)
										      where
										      	 parent_cid=" .$item->GetParentId(). "
										      ORDER BY FIELD(cid, " .implode(',', $idsToSort). ")
									     ");

        return true;
    }

    public function Delete($filter = null)
    {
        $items = $this->GetList($filter);

        $rootElem = $this->GetRoot();
        if ($rootElem && (isset($items[$rootElem->GetId()])))
        {
			$this->_source->exec("TRUNCATE TABLE %%categories%%");
			$this->CleanCache();
			return true;
        }

        $updRows = 0;
        foreach ($items as $item)
        {
	        $num = $this->_source->exec("delete
	        										cats
	        									from
	        										%%categories%% cats
	        									where
	        										cats.fullurl LIKE " . $this->_source->quote($item->GetFullUrl(). "%"));

	        if ($num)
	        {
				$updRows++;
	        }
        }

		if ($updRows)
		{
			$this->CleanCache();
		}

        return $updRows;
    }
}